using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity.ModelConfiguration.Configuration;
using System.Linq.Expressions;
using System.Reflection;
using Taobao.Tools2.Domain.EntityFramework.Mappings.Strategies;

namespace Taobao.Tools2.Domain.EntityFramework.Mappings
{
    #region Class ModelMapping
    internal static class ModelMapping
    {
        private const string EntityTablePrefix = "Tbl";
        private const string AcTablePrefix = "Ac";
        private const string XrefTablePrefix = "Xref";
        private const string FoeignKeySuffix = "Id";
        private const string RelationDomainSuffix = "Relation";

        internal static readonly Lazy<IMappingStrategy> AddUnderscoresBetweenWordsThenToLowerMappingStrategy =
            new Lazy<IMappingStrategy>(() => new CompositeMappingStrategy
            {
                Strategies = new IMappingStrategy[]
                                                                      {
                                                                          new AddUnderscoresBetweenWordsMappingStrategy()
                                                                          ,
                                                                          new ToLowerMappingStrategy()
                                                                      }
            });

        internal static readonly Lazy<IMappingStrategy> EntityTableNameMappingStrategy =
            new Lazy<IMappingStrategy>(() => new CompositeMappingStrategy
            {
                Strategies = new List<IMappingStrategy>(new[]
                                                                                                 {
                                                                                                     new AddPrefixMappingStrategy
                                                                                                         (EntityTablePrefix)
                                                                                                     ,
                                                                                                     AddUnderscoresBetweenWordsThenToLowerMappingStrategy
                                                                                                         .Value
                                                                                                 })
            });

        internal static readonly Lazy<IMappingStrategy> AcEntityTableNameMappingStrategy =
            new Lazy<IMappingStrategy>(() => new CompositeMappingStrategy
            {
                Strategies = new List<IMappingStrategy>(new[]
                                                                                                 {
                                                                                                     new AddPrefixMappingStrategy
                                                                                                         (AcTablePrefix)
                                                                                                     ,
                                                                                                     EntityTableNameMappingStrategy.Value
                                                                                                 })
            });

        internal static readonly Lazy<IMappingStrategy> XrefAcTableNameMappingStrategy = new Lazy<IMappingStrategy>(
                () => new CompositeMappingStrategy
                        {
                            Strategies = new List<IMappingStrategy>()
                                            { 
                                                new AddPrefixMappingStrategy(AcTablePrefix),
                                                new AddPrefixMappingStrategy(XrefTablePrefix),
                                                AddUnderscoresBetweenWordsThenToLowerMappingStrategy.Value
                                            }
                        }
            );

        internal static readonly Lazy<IMappingStrategy> XrefTableNameMappingStrategy = new Lazy<IMappingStrategy>(
                () => new CompositeMappingStrategy
                {
                    Strategies = new List<IMappingStrategy>()
                                            {
                                                new AddPrefixMappingStrategy(XrefTablePrefix),
                                                new RemoveSuffixMappingStrategy(RelationDomainSuffix),
                                                AddUnderscoresBetweenWordsThenToLowerMappingStrategy.Value
                                            }
                }
            );

        internal static readonly Lazy<IMappingStrategy> ForeignKeyNoTableNamePrefixMappingStrategy = new Lazy<IMappingStrategy>(() => new CompositeMappingStrategy
        {
            Strategies = new List<IMappingStrategy>()
                                            {  
                                                new AddSuffixMappingStrategy(FoeignKeySuffix),
                                                AddUnderscoresBetweenWordsThenToLowerMappingStrategy.Value
                                            }
        });

        internal static readonly Lazy<IMappingStrategy> AddFoeignKeySuffixMappingStrategy =
            new Lazy<IMappingStrategy>(() => new AddSuffixMappingStrategy(FoeignKeySuffix));
    }
    #endregion

    #region Class ModelMapping<TModel>
    public class ModelMapping<TModel> : EntityTypeConfiguration<TModel>, IMapping
        where TModel : class
    {
        protected static readonly Lazy<IMappingStrategy> AddTypeNameAsPrefixMappingStrategy =
            new Lazy<IMappingStrategy>(() => new AddPrefixMappingStrategy(typeof (TModel).Name));

        protected static readonly Lazy<IMappingStrategy> PrimitiveColumnNameMappingStrategy =
            new Lazy<IMappingStrategy>(() => new CompositeMappingStrategy
                                                 {
                                                     Strategies = new List<IMappingStrategy>(new[]
                                                                                                 {
                                                                                                     AddTypeNameAsPrefixMappingStrategy
                                                                                                         .Value,
                                                                                                     ModelMapping.
                                                                                                         AddUnderscoresBetweenWordsThenToLowerMappingStrategy
                                                                                                         .Value
                                                                                                 })
                                                 });

        protected static readonly Lazy<IMappingStrategy> ForeignKeyColumnNameMappingStrategy =
            new Lazy<IMappingStrategy>(() => new CompositeMappingStrategy
                                                 {
                                                     Strategies = new List<IMappingStrategy>(new[]
                                                                                                 {
                                                                                                     ModelMapping.AddFoeignKeySuffixMappingStrategy.Value,
                                                                                                     PrimitiveColumnNameMappingStrategy
                                                                                                         .Value
                                                                                                 })
                                                 });

        #region Ctors
        protected ModelMapping(IMappingStrategy tableNameMappingStrategy, IMappingStrategy primitiveColumnNameMappingStrategy, IMappingStrategy foreignKeyColumnNameMappingStrategy)
        {
            MapClassToTable(this, tableNameMappingStrategy);
            MapPrimitivePropertiesToColumns(this, primitiveColumnNameMappingStrategy);
             
        }

        protected ModelMapping(IMappingStrategy tableNameMappingStrategy, IMappingStrategy primitiveColumnNameMappingStrategy)
            : this(tableNameMappingStrategy, primitiveColumnNameMappingStrategy, ForeignKeyColumnNameMappingStrategy.Value)
        {
            
        }

        protected ModelMapping(IMappingStrategy tableNameMappingStrategy)
            : this(tableNameMappingStrategy , null)
        {
            
        }

        protected ModelMapping()
            : this(null, null)
        {

        }
        #endregion

        public void RegistTo(ConfigurationRegistrar configurationRegistrar)
        {
            configurationRegistrar.Add(this);
        }

        protected void MapClassToTable(EntityTypeConfiguration<TModel> configuration, IMappingStrategy tableNameMappingStrategy)
        {
            if (configuration == null || tableNameMappingStrategy == null)
                return;
            configuration.ToTable(tableNameMappingStrategy.To(typeof(TModel).Name));
        }

        protected void MapPropertyToForeignKeyColumn<TTargetModel>(DependentNavigationPropertyConfiguration<TModel> configuration, IMappingStrategy columnNameMappingStrategy)
        {
            MapPropertyToForeignKeyColumn(configuration, columnNameMappingStrategy, typeof (TTargetModel).Name);
        }

        protected void MapPropertyToForeignKeyColumn(ForeignKeyNavigationPropertyConfiguration configuration, IMappingStrategy columnNameMappingStrategy, string propertyName)
        {
            if (configuration == null || columnNameMappingStrategy == null)
                return;
            configuration.Map(config => config.MapKey(columnNameMappingStrategy.To(propertyName)));
        }

        protected void MapPropertyToForeignKeyColumn<TTargetModel>(ForeignKeyNavigationPropertyConfiguration configuration, IMappingStrategy columnNameMappingStrategy)
        {
            MapPropertyToForeignKeyColumn(configuration, columnNameMappingStrategy, typeof(TTargetModel).Name);
        }
         
        protected void MapCollectionPropertyToForeignKeyColumn<TTargetModel>(DependentNavigationPropertyConfiguration<TTargetModel> configuration)
            where TTargetModel : class
        {
            MapCollectionPropertyToForeignKeyColumn(configuration, (typeof (TModel).Name));
        }

        protected void MapCollectionPropertyToForeignKeyColumn<TTargetModel>(DependentNavigationPropertyConfiguration<TTargetModel> configuration, string propertyName)
            where TTargetModel : class
        {
            if (configuration == null)
                return;
            var columnNameMappingStrategy = new CompositeMappingStrategy
            {
                Strategies = new[]
                                                                     {
                                                                         //AddTypeNameAsPrefixMappingStrategy.Value,
                                                                         new AddPrefixMappingStrategy(
                                                                             typeof (TTargetModel).Name),
                                                                         ModelMapping.AddFoeignKeySuffixMappingStrategy.
                                                                             Value,
                                                                         ModelMapping.
                                                                             AddUnderscoresBetweenWordsThenToLowerMappingStrategy
                                                                             .Value
                                                                     }
            };
            configuration.Map(config => config.MapKey(columnNameMappingStrategy.To(propertyName)));
        }

        protected void MapPrimitivePropertiesToColumns(EntityTypeConfiguration<TModel> configuration, IMappingStrategy columnNameMappingStrategy)
        {
            if (configuration == null || columnNameMappingStrategy == null)
                return;
            var entityType = typeof (TModel);
            var properties =
                entityType.GetProperties().Where(
                    p => p.CanRead
                      && p.CanWrite
                      && p.GetCustomAttributes(typeof(NotMappedAttribute), false).Count() == 0);
            foreach (var propertyInfo in properties)
            {
                var entityExpression = Expression.Parameter(typeof (TModel), "model");
                var propertyExpression = Expression.Property(entityExpression, propertyInfo);                 
               
                if (propertyInfo.PropertyType.Equals(typeof(Int16)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Int16>>(
                        Expression.Convert(propertyExpression, typeof(Int16)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Int16?)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Int16?>>(
                        Expression.Convert(propertyExpression, typeof(Int16?)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Int32)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Int32>>(
                        Expression.Convert(propertyExpression, typeof(Int32)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Int32?)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Int32?>>(
                        Expression.Convert(propertyExpression, typeof(Int32?)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Int64)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Int64>>(
                        Expression.Convert(propertyExpression, typeof(Int64)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Int64?)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Int64?>>(
                        Expression.Convert(propertyExpression, typeof(Int64?)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Single)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Single>>(
                        Expression.Convert(propertyExpression, typeof(Single)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Single?)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Single?>>(
                        Expression.Convert(propertyExpression, typeof(Single?)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Double)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Double>>(
                        Expression.Convert(propertyExpression, typeof(Double)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Double?)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Double?>>(
                        Expression.Convert(propertyExpression, typeof(Double?)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(DateTime)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, DateTime>>(
                        Expression.Convert(propertyExpression, typeof(DateTime)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(DateTime?)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, DateTime?>>(
                        Expression.Convert(propertyExpression, typeof(DateTime?)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(DateTimeOffset)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, DateTimeOffset>>(
                        Expression.Convert(propertyExpression, typeof(DateTimeOffset)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(DateTimeOffset?)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, DateTimeOffset?>>(
                        Expression.Convert(propertyExpression, typeof(DateTimeOffset?)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(TimeSpan)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, TimeSpan>>(
                        Expression.Convert(propertyExpression, typeof(TimeSpan)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(TimeSpan?)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, TimeSpan?>>(
                        Expression.Convert(propertyExpression, typeof(TimeSpan?)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Decimal)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Decimal>>(
                        Expression.Convert(propertyExpression, typeof(Decimal)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Decimal?)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Decimal?>>(
                        Expression.Convert(propertyExpression, typeof(Decimal?)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Boolean)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Boolean>>(
                        Expression.Convert(propertyExpression, typeof(Boolean)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Boolean?)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Boolean?>>(
                        Expression.Convert(propertyExpression, typeof(Boolean?)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Char)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Char>>(
                        Expression.Convert(propertyExpression, typeof(Char)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Char?)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Char?>>(
                        Expression.Convert(propertyExpression, typeof(Char?)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Guid)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Guid>>(
                        Expression.Convert(propertyExpression, typeof(Guid)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(Guid?)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, Guid?>>(
                        Expression.Convert(propertyExpression, typeof(Guid?)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else if (propertyInfo.PropertyType.Equals(typeof(String)))
                {
                    var propertyLambdaExpression = Expression.Lambda<Func<TModel, String>>(
                        Expression.Convert(propertyExpression, typeof(String)), entityExpression);
                    configuration.Property(propertyLambdaExpression).HasColumnName(columnNameMappingStrategy.To(propertyInfo.Name));
                }
                else
                {
                    //do nothing
                    //throw new NotSupportedException(
                    //    "Can not map property {0} to column as the type of the property {1} is not supported");
                }
            }
        }
    }

    //public class LastModifiedUserModelMapping<TModel> : ModelMapping<TModel>
    //    where TModel : class , ILastModifiedUser
    //{
    //    #region Ctors
    //    protected LastModifiedUserModelMapping(IMappingStrategy tableNameMappingStrategy, IMappingStrategy primitiveColumnNameMappingStrategy)
    //        :base(tableNameMappingStrategy, primitiveColumnNameMappingStrategy)
    //    {
    //        MapPropertyToForeignKeyColumn(HasRequired(model => model.LastModifiedUser).WithMany(), ForeignKeyColumnNameMappingStrategy.Value, "LastModifiedUser");
    //    }

    //    protected LastModifiedUserModelMapping(IMappingStrategy tableNameMappingStrategy)
    //        : this(tableNameMappingStrategy , null)
    //    {
            
    //    }

    //    protected LastModifiedUserModelMapping()
    //        : this(null, null)
    //    {

    //    }
    //    #endregion
    //}
    #endregion

    #region MultiDerivedModelMapping<TModel>
    public class MultiDerivedModelMapping<TModel> : ModelMapping<TModel>
        where TModel : class
    {
        public MultiDerivedModelMapping(IList<IDerivedEntityTypeConfigurationAdapter<TModel>> entityTypeConfigurationAdapters)
            : this(entityTypeConfigurationAdapters, ModelMapping.EntityTableNameMappingStrategy.Value, PrimitiveColumnNameMappingStrategy.Value)
        {
        }

        public MultiDerivedModelMapping(IList<IDerivedEntityTypeConfigurationAdapter<TModel>> entityTypeConfigurationAdapters,
                                        IMappingStrategy tableNameMappingStrategy,
                                        IMappingStrategy primitiveColumnNameMappingStrategy)
            : this(entityTypeConfigurationAdapters, tableNameMappingStrategy, primitiveColumnNameMappingStrategy, ForeignKeyColumnNameMappingStrategy.Value)
        {
        }

        public MultiDerivedModelMapping(IList<IDerivedEntityTypeConfigurationAdapter<TModel>> entityTypeConfigurationAdapters, 
                                        IMappingStrategy tableNameMappingStrategy, 
                                        IMappingStrategy primitiveColumnNameMappingStrategy,
                                        IMappingStrategy foreignKeyColumnNameMappingStrategy)
            : base(tableNameMappingStrategy, primitiveColumnNameMappingStrategy, foreignKeyColumnNameMappingStrategy)
        {
            foreach (var adapter in entityTypeConfigurationAdapters)
            {
                var configuration = adapter.BuildEntityTypeConfiguration(this);
                if (!adapter.HasMappedTable)
                    MapClassToTable(configuration, ModelMapping.EntityTableNameMappingStrategy.Value);
            }
        }
    }
    #endregion
     
}